查看原文
其他

Swift 5.2 的几个新特性

Paul Hudson 小集 2022-08-27

作者 | Paul Hudson 
来源 | Hacking with Swift

随着 Xcode 11.4 的发布,Swift 5.2 也正式到来,新的版本包含少量语言层面的更新,代码大小和内存使用的减少,以及新的诊断体系结构。新的诊断体系结构可以帮助我们更快地理解和解决错误。

在本文中,将通过具体的实例来说明 Swift 5.2 中的一些新特性,以便让您对这些更新有一个更清晰的认识。我建议你通过链接到对应的 Swift Evolution 中,可获取更多信息。

将 Key Path 表达式作为函数

SE-0249 介绍了一个奇特的快捷方式,让我们可以在一些特定情况下以函数调用的方式使用 keypath。

这个 Evolution 提案提议在使用类型为 (Root) -> Value 的函数的地方,可以使用更简便的 \Root.value 来代替,也就是说如果将一个 Car 类型实例作为参数传递给方法,然后返回这个 Car 实例的 licensePlate 属性值,则现在可以直接使用 Car.licensePlate 来代替方法调用。

让我们举个例子,这里有一个 User 类型,定义了 4 个属性:

struct User {
let name: String
let age: Int
let bestFriend: String?

var canVote: Bool {
age >= 18
}
}

我们可以创建该结构的一些实例并将其放入数组,如下所示:

let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]

我们的目标是获取所有用户的 name 值的数组,那么可以使用类似以下的 key path 来实现:

let userNames = users.map(\.name)
print(userNames)

在此之前,我们必须通过闭包来手动获取 name 值,即

let oldUserNames = users.map { $0.name }

在其它地方也可以使用同样的方法,即以类型实例为参数,并返回其某一属性值的任何地方,都可以使用这种 key path。例如,以下代码返回所有可以投票的用户:

let voters = users.filter(\.canVote)

而以下代码则返回 bestFriend 不为空的用户的好友:

let bestFriends = users.compactMap(\.bestFriend)

可调用类型

SE-0253 在 Swift 中引入了可静态调用的值,这是一种有趣的说法,即如果值的类型实现了一个名为 callAsFunction() 的方法,则现在可以直接以函数的方式来调用该类型的值。无需遵循任何特殊协议即可让这种行为生效;只需要将该方法添加到您的类型中即可。

例如,我们可以创建一个Dice 结构体,它有两个属性:lowerBound 和 upperBound ,然后在类型中添加 callAsFunction ,这样每次调用 Dice 的值时都会得到一个随机值:

struct Dice {
var lowerBound: Int
var upperBound: Int

func callAsFunction() -> Int {
(lowerBound...upperBound).randomElement()!
}
}

let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)

上面的代码将打印一个从1到6的随机数,这与直接使用 callAsFunction() 的效果是相同。例如,我们可以这样调用它:

let d12 = Dice(lowerBound: 1, upperBound: 12)
let roll2 = d12.callAsFunction()
print(roll2)

Swift会根据定义 callAsFunction() 的方式自动调整调用方式。例如,可以根据需要添加任意数量的参数,可以控制返回值,甚至可以根据需要将方法标记为 mutating

例如,以下代码将创建一个 StepCounter 结构,该结构跟踪某人已经走了多远,并报告他们是否达到了 10,000 步的目标:

struct StepCounter {
var steps = 0

mutating func callAsFunction(count: Int) -> Bool {
steps += count
print(steps)
return steps > 10_000
}
}

var steps = StepCounter()
let targetReached = steps(count: 10)

还有更高级的用法,callAsFunction() 支持 throws 和 rethrows ,甚至可以在单个类型上定义多个 callAsFunction() 方法,Swift 会根据调用方式选择正确的方法,就像常规重载一样。

下标可以声明默认参数值

将自定义下标添加到类型时,现在可以将默认参数用于任何参数。例如,如果我们有一个带有自定义下标的 PoliceForce 结构体,以从部队中获取军官信息,我们可以添加一个 default 参数,以便在有人尝试读取数组范围之外的索引时返回默认值:

struct PoliceForce {
var officers: [String]

subscript(index: Int, default default: String = "Unknown") -> String {
if index >= 0 && index < officers.count {
return officers[index]
} else {
return `default`
}
}
}

let force = PoliceForce(officers: ["Amy", "Jake", "Rosa", "Terry"])
print(force[0])
print(force[5])

以上代码将打印 "Amy",然后打印 "Unknown",后者是因为数组越界而输出默认值。需要注意的是,如果要让参数可用,则需要重复两次标签,因为下标是不使用参数标签的。

因此,由于我在下标中使用了 default default ,所以可以使用如下自定义值:

print(force[-1, default: "The Vulture"])

lazy 序列的多个 filter 的顺序现在颠倒了

Swift 5.2 中有一个微小变化有可能导致你的功能中断:如果您使用诸如数组之类的惰性序列,并对其应用多个过滤器,则这些过滤器现在将以相反的顺序运行。

例如,下面的代码有一个过滤器,该过滤器选择以S开头的名称,然后另一个过滤器打印出名称,然后返回 true:

let people = ["Arya", "Cersei", "Samwell", "Stannis"]
.lazy
.filter { $0.hasPrefix("S") }
.filter { print($0); return true }
_ = people.count

在Swift 5.2和更高版本中,上述代码将打印“ Samwell”和“ Stannis”,因为在第一个过滤器运行之后,这两个字符串是剩下的进入第二个过滤器的唯一名称。但是在Swift 5.2之前,则会打印所有四个名称,因为第二个过滤器将在第一个过滤器之前运行。这会令人困惑,因为如果删除 lazy,那么无论Swift 是哪个版本,代码始终只会返回Samwell和Stannis。

这很有问题,因为其行为取决于代码的运行位置:如果您在iOS 13.3或更早版本或macOS 10.15.3或更早版本上运行Swift 5.2代码,则以原始的方式执行,但是在较新的操作系统上运行的相同代码将提供新的正确行为。

因此,此更改可能会导致代码意外中断,但希望这只是短期内的麻烦。

新的改进的诊断体系

Swift 5.2 引入了一种新的诊断体系结构,该体系结构旨在在发生编码错误时提高 Xcode 发出的错误消息的质量和准确性。这在使用SwiftUI代码时尤其明显,因为Swift经常会误报错误消息。

例如,考虑如下代码:

struct ContentView: View {
@State private var name = 0

var body: some View {
VStack {
Text("What is your name?")
TextField("Name", text: $name)
.frame(maxWidth: 300)
}
}
}

这里试图将 TextField 视图绑定到整数 @State 属性,这样做是无效的。在 Swift 5.1 中,报出的错误是 frame() 修饰符出错,提示的是 'Int' is not convertible to 'CGFloat? ,但是在 Swift 5.2 及更高版本中,这可以正确地识别是 $name 的绑定错误:Cannot convert value of type Binding<Int> to expected argument type Binding<String>


推荐阅读
• Xcode 11.4 新特性概览
• Swift 5.3 路线图
• 如何实现 iOS App 的冷启动优化
• SDWebImage 4.x & 5.x 对 GIF 类型的处理问题



职位分享 ‧ 支付 ‧ 客户端小程序容器技术

【 Base】

上海、杭州

【岗位职责】

1、负责支付宝小程序容器架构设计、开发及性能调优;
2、负责支付宝小程序能力的延展,如UI组件的开发,业务服务的开发,各种类型设备的支持等;
3、负责支付宝小程序,native引擎的选型和开发;
4、负责支付宝客户端、iOT端和服务端,云端结合的组件和工具的拓展;
5、负责阿里小程序生态中台的基础设施开发

【岗位要求】

1、三年以上Android、iOS客户端或者 C++ 开发经验;
2、精通至少一门编程语言,包括但不仅限于:Java、C、C++、Object C、Kotlin、Swift、Dart、JavaScript等
3、熟练掌握计算机系统结构、操作系统原理、数据结构、算法,网络,数据库,计算机安全等知识;
4、了解移动客户端特性,了解Android/iOS SDK Framework,熟悉软件设计模式、敏捷开发;
5、优秀的逻辑思维能力、分析能力、沟通能力,有强烈的责任感和良好的团队合作精神;

【重点】

HC 大量(10+),客户端 Android、iOS、C++ 都要,P6+ ~ P8

【简历投递】

yixi.zyf@antfin.com,微信 shin_87224330


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存